package ybl.wechat;

import java.io.File;
import java.io.IOException;

import org.jdbi.v3.core.Jdbi;

import ybl.wechat.entities.AccessToken;
import ybl.wechat.entities.QrCode;

import java.sql.Timestamp;

public class Storage {

    private static Jdbi initDB() throws IOException {
        String workingDirectory = System.getProperty("user.dir");
        String dbFilePath = workingDirectory + "/dbs/wechat.db";
        File dbFile = new File(dbFilePath);
        if (!dbFile.exists()) {
            dbFile.getParentFile().mkdirs(); // 创建父目录
            dbFile.createNewFile(); // 创建新文件
        }

        String jdbcUrl = "jdbc:sqlite:" + dbFilePath;
        return Jdbi.create(jdbcUrl);
    }

    /**
     * 创建所需的数据库表。
     * 
     * @param jdbi Jdbi 实例。
     */
    private static void createTables(Jdbi jdbi) {
        String sql = "CREATE TABLE IF NOT EXISTS AccessToken (\n"
                + " appid text PRIMARY KEY,\n"
                + " accessToken text NOT NULL,\n"
                + " updateAt timestamp NOT NULL\n"
                + ");";
        jdbi.useHandle(handle -> {
            handle.execute(sql);
        });
    }

    /**
     * 保存或更新访问令牌。
     * 
     * @param appid       应用程序的ID。
     * @param accessToken 访问令牌。
     * @param updateAt    更新时间。
     * @throws IOException
     */
    public static void saveAndUpdateAccessToken(String appid, String accessToken, Timestamp updateAt)
            throws IOException {
        Jdbi jdbi = initDB();
        createTables(jdbi);

        // 尝试更新记录
        int updated = jdbi.withHandle(handle -> handle
                .createUpdate(
                        "UPDATE AccessToken SET accessToken = :accessToken, updateAt = :updateAt WHERE appid = :appid")
                .bind("appid", appid)
                .bind("accessToken", accessToken)
                .bind("updateAt", updateAt)
                .execute());

        // 如果没有记录被更新，则插入新记录
        if (updated == 0) {
            jdbi.useHandle(handle -> handle.createUpdate(
                    "INSERT INTO AccessToken (appid, accessToken, updateAt) VALUES (:appid, :accessToken, :updateAt)")
                    .bind("appid", appid)
                    .bind("accessToken", accessToken)
                    .bind("updateAt", updateAt)
                    .execute());
        }
    }

    /**
     * 查询指定应用程序的访问令牌，并返回访问令牌。
     * 
     * @param appid 应用程序的ID。
     * @return 访问令牌。如果没有找到访问令牌，那么返回null。
     * @throws IOException
     */
    public static AccessToken getAccessToken(String appid) throws IOException {
        Jdbi jdbi = initDB();
        createTables(jdbi);
        return jdbi.withHandle(handle -> handle.createQuery("SELECT * FROM AccessToken WHERE appid = :appid")
                .bind("appid", appid)
                .mapToBean(AccessToken.class)
                .findOne()
                .orElse(null));
    }

    /**
     * 创建QrCode表来存储二维码信息。
     * 
     * @param jdbi Jdbi 实例。
     */
    private static void createQrCodeTable(Jdbi jdbi) {
        String sql = "CREATE TABLE IF NOT EXISTS QrCode (\n"
                + " ticket text PRIMARY KEY,\n"
                + " expire_seconds integer NOT NULL,\n"
                + " createdAt timestamp NOT NULL,\n"
                + " flag boolean NOT NULL\n"
                + ");";
        jdbi.useHandle(handle -> {
            handle.execute(sql);
        });
    }

    /**
     * 保存或更新二维码信息。如果二维码已存在，则更新其信息，否则插入新的二维码记录。
     * 
     * @param ticket         二维码的票据，作为唯一标识。
     * @param expire_seconds 二维码的有效时间（秒）。
     * @param createdAt      二维码的创建时间。
     * @param flag           二维码的标志位，用于表示某些状态。
     * @throws IOException 如果初始化数据库或执行数据库操作时出错。
     */
    public static void saveOrUpdateQrCode(String ticket, int expire_seconds, Timestamp createdAt, boolean flag)
            throws IOException {
        Jdbi jdbi = initDB();
        createQrCodeTable(jdbi);

        int updated = jdbi.withHandle(handle -> handle
                .createUpdate(
                        "UPDATE QrCode SET expire_seconds = :expire_seconds, createdAt = :createdAt, flag = :flag WHERE ticket = :ticket")
                .bind("ticket", ticket)
                .bind("expire_seconds", expire_seconds)
                .bind("createdAt", createdAt)
                .bind("flag", flag)
                .execute());

        if (updated == 0) {
            jdbi.useHandle(handle -> handle.createUpdate(
                    "INSERT INTO QrCode (ticket, expire_seconds, createdAt, flag) VALUES (:ticket, :expire_seconds, :createdAt, :flag)")
                    .bind("ticket", ticket)
                    .bind("expire_seconds", expire_seconds)
                    .bind("createdAt", createdAt)
                    .bind("flag", flag)
                    .execute());
        }
    }

    /**
     * 根据ticket查询二维码信息。
     * 
     * @param ticket 二维码的票据，用于查找特定的二维码记录。
     * @return 返回找到的QrCode对象。如果没有找到，返回null。
     * @throws IOException 如果初始化数据库或执行数据库操作时出错。
     */
    public static QrCode getQrCode(String ticket) throws IOException {
        Jdbi jdbi = initDB();
        createQrCodeTable(jdbi);
        return jdbi.withHandle(handle -> handle.createQuery("SELECT * FROM QrCode WHERE ticket = :ticket")
                .bind("ticket", ticket)
                .mapToBean(QrCode.class)
                .findOne()
                .orElse(null));
    }

    /**
     * 根据ticket删除二维码信息。
     * 
     * @param ticket 二维码的票据，指定要删除的二维码记录。
     * @throws IOException 如果初始化数据库或执行数据库操作时出错。
     */
    public static void deleteQrCode(String ticket) throws IOException {
        Jdbi jdbi = initDB();
        createQrCodeTable(jdbi);
        jdbi.useHandle(handle -> handle.execute("DELETE FROM QrCode WHERE ticket = :ticket", ticket));
    }

    /**
     * 查询指定票据的二维码，如果该二维码已过期，则返回 null。
     * 
     * @param ticket 二维码的票据。
     * @return 未过期的二维码，如果二维码不存在或已过期，则返回 null。
     * @throws IOException 如果初始化数据库或执行数据库操作时发生错误。
     */
    public static QrCode getQrCodeInTTL(String ticket) throws IOException {
        Jdbi jdbi = initDB();
        createQrCodeTable(jdbi);

        return jdbi.withHandle(handle -> handle.createQuery(
                "SELECT * FROM QrCode WHERE ticket = :ticket AND (strftime('%s','now') - (createdAt/1000)) <= expire_seconds")
                .bind("ticket", ticket)
                .mapToBean(QrCode.class)
                .findOne()
                .orElse(null));
    }

    /**
     * 删除过期的二维码记录。
     * 假设 `expire_seconds` 是从创建时刻起，二维码有效的总秒数。
     * 
     * @throws IOException 如果初始化数据库或执行数据库操作时发生错误。
     */
    public static void deleteExpiredQrCodes() throws IOException {
        Jdbi jdbi = initDB();
        createQrCodeTable(jdbi);

        String sql = "DELETE FROM QrCode WHERE (strftime('%s','now') - (createdAt/1000)) > expire_seconds";
        jdbi.useHandle(handle -> {
            handle.execute(sql);
        });
    }

}
